/*************************************************************************
	> File Name: NfaInterpretor.hpp
	> Author: H.M. Lau
	> Mail: hm.lau@outlook.com
	> Created Time: 2023/11/03 15:14
 ************************************************************************/

#pragma once

#include <iostream>
#include <unordered_set>
#include <stack>
#include "log.hpp"
#include "nfa.hpp"
#include "input.hpp"

namespace nfa
{
    class NfaInterpretor
    {
    public:
        NfaInterpretor () {}

        void nfaMatchStr(nfa::Nfa* start)
        {
            _input.newFile();
            std::unordered_set<nfa::Nfa*> current = { start };
            auto next = epsilonClousre(current);
#ifdef DEBUG_SHOW
            printEClosure(current, next);
#endif
            char c;
            bool lastAccepted = false;
            std::string inputStr = "";
            while ((c = _input.advance()) != '\n') {
                current = move(next, c);
#ifdef DEBUG_SHOW
                printMove(next, current, c);
#endif
                next = epsilonClousre(current);
#ifdef DEBUG_SHOW
                printEClosure(current, next);
#endif
                if (!next.empty()) {
                    if (hasAcceptState(next)) lastAccepted = true;
                } else {
                    break;
                }
                inputStr += c;
            }
            if (lastAccepted) log::logMessage(log::LOG_LEVEL::_INFO, "The Nfa Machine can recognise string : %s", inputStr.c_str());
        }

        std::unordered_set<nfa::Nfa*> epsilonClousre(const std::unordered_set<nfa::Nfa*>& input) const
        {
            std::unordered_set<nfa::Nfa*> next;
            if (input.empty()) {
                log::logMessage(log::LOG_LEVEL::_ERROR, "input is empty");
                return next;
            }
            std::stack<nfa::Nfa*> st;
            for (const auto& e : input) {
                st.push(e);
                next.insert(e);
            }
            while (!st.empty()) {
                auto t = st.top();
                st.pop();
                if (t->getEdge() != nfa::Recv::EPSILON) continue;
                if (t->_next1 && next.find(t->_next1) == next.end()) {
                    st.push(t->_next1);
                    next.insert(t->_next1);
                }
                if (t->_next2 && next.find(t->_next2) == next.end()) {
                    st.push(t->_next2);
                    next.insert(t->_next2);
                }
            }
            return next;
        }

        bool hasAcceptState(const std::unordered_set<nfa::Nfa*>& input) const
        {
            if (input.empty()) return false;
            bool isAccepted = false;
            std::string acceptedState = "Accept State:";
            for (const auto& e : input)
                if (!e->_next1 && !e->_next2) {
                    isAccepted = true;
                    acceptedState += std::to_string(e->getNum());
                }
            if (isAccepted) log::logMessage(log::LOG_LEVEL::_DEBUG, "%s", acceptedState.c_str());
            return isAccepted;
        }

        std::unordered_set<nfa::Nfa*> move(const std::unordered_set<nfa::Nfa*>& input, const uint8_t& c) const 
        {
            if (input.empty()) {
                log::logMessage(log::LOG_LEVEL::_ERROR, "input is empty");
                exit(2);
            }
            std::unordered_set<nfa::Nfa*> next;
            for (const auto& e : input) 
                if (e->getEdge() == nfa::Recv::CCL && e->getSet().find(c) != e->getSet().end()) next.insert(e->_next1);
            return next;
        }

        void printEClosure(const std::unordered_set<nfa::Nfa*>& input, const std::unordered_set<nfa::Nfa*>& output) const
        {
            std::cout << "Epsilon Closure : { ";
            for (const auto& e : input) std::cout << e->getNum() << " ";
            std::cout << "} => { ";
            for (const auto& e : output) std::cout << e->getNum() << " ";
            std::cout << "}" << std::endl;
        }

        void printMove(const std::unordered_set<nfa::Nfa*>& input, const std::unordered_set<nfa::Nfa*>& output, const uint8_t& c) const
        {
            std::cout << "Move : { ";
            for (const auto& e : input) std::cout << e->getNum() << " ";
            std::cout << "} => { ";
            for (const auto& e : output)  std::cout << e->getNum() << " ";
            std::cout << "}" << " receive:" << (char)c << std::endl;
        }

    private:
        Input _input;
    };
}
